#include "fileidmapper.h"

#include <QStandardPaths>
#include <QDir>
#include <QFileInfo>
#include <QString>
#include <QHash>

#include <QDebug>

#include <leveldb/db.h>
#include <leveldb/write_batch.h>
#include <iostream>

#include <string>

class UUIDFileDatabase;

FileIDMapper *global_instance = nullptr;
std::string dbdir;
QHash<QString, UUIDFileDatabase*> uuidTable;

std::string blackDirList[] = {"/var", "/tmp", "/data/root", "/root", "/run", "/etc", "/systemd"};
std::string blackSuffixList[] = {".db-wal", ".log", ".db", ".bin", ".xlog", ".xsession-errors", ".tmp", ".bash_history", ".material", "temp", "~", ".dat"};
std::string blackCmdList[] = {"hardlink", "GoImcore", "ninja", "make", "bpftool"};

class UUIDFileDatabase
{
public:
    UUIDFileDatabase(const std::string &uuid){
        std::cout << "-------------ukui file database constructor start: " << uuid << "--------------" << std::endl;
        std::string dbpath = FileIDMapper::databaseDir();
        dbpath += "/";
        dbpath += uuid;
        leveldb::Options options;
        options.create_if_missing = true;
        leveldb::Status status = leveldb::DB::Open(options, dbpath, &db);
        if (!status.ok()) {
            std::cerr << status.ToString();
        } else {
            auto iter = db->NewIterator(leveldb::ReadOptions());
            iter->SeekToFirst();
            if (!iter->Valid()) {
                std::cout << "database is empty, init currentIncrementID as 0" << std::endl;
                uint64_t initstatus = 0;
                leveldb::Slice value((char*)&initstatus, sizeof(uint64_t));
                db->Put(leveldb::WriteOptions(), "currentIncrementID", value);
            } else {
                std::cout << "get saved increment id";
                leveldb::ReadOptions options;
                std::string tmp;
                leveldb::Status status = db->Get(options, "currentIncrementID", &tmp);
                if (!status.ok()) {
                    std::cerr << status.ToString();
                } else {
                    currentIncrementID = *(uint64_t*)(tmp.data());
                    std::cout << "currentIncrementID is " << currentIncrementID << std::endl;

                    auto iter = db->NewIterator(options);
                    for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
                        if (iter->key().size() != sizeof(uint64_t)) {
                            continue;
                        }
                        uint64_t inode = *(uint64_t*)(iter->key().data());
                        uint64_t id = *(uint64_t*)(iter->value().data());
                        ids.insert(inode, id);
                        inodes.insert(id, inode);
                    }

                    qDebug() << ids;
                }
            }
        }
    }

    uint64_t addFile(uint64_t inode)
    {
        auto id = ids.value(inode);
        if (id) {
            return id;
        }

        currentIncrementID++;
        auto option = leveldb::WriteOptions();
        ids.insert(inode, currentIncrementID);
        inodes.insert(currentIncrementID, inode);
        leveldb::Slice key((char*)&inode, sizeof(uint64_t));
        leveldb::Slice value((char*)&currentIncrementID, sizeof(uint64_t));
        db->Put(option, key, value);
        leveldb::Slice idValue((char*)&currentIncrementID, sizeof(uint64_t));
        db->Put(option, "currentIncrementID", idValue);
        return currentIncrementID;
    }
    bool deleteFile(uint64_t inode)
    {
        auto id = ids.value(inode, 0);
        if (!id)
            return false;

        ids.remove(inode);
        inodes.remove(id);
        leveldb::Slice key((char*)&inode, sizeof(uint64_t));
        db->Delete(leveldb::WriteOptions(), key);
        return true;
    }
    bool changeFileInode(uint64_t id, uint64_t inode)
    {
        if (!id) {
            return false;
        }
        
        ids.insert(inode, id);
        inodes.remove(id);
        inodes.insert(id, inode);
        leveldb::Slice key((char*)&inode, sizeof(uint64_t));
        leveldb::Slice value((char*)&id, sizeof(uint64_t));
        db->Put(leveldb::WriteOptions(), key, value);
        return true;
    }

    uint64_t getFileIDByInode(uint64_t inode, bool allocateNonExist)
    {
        uint64_t id = ids.value(inode, 0);
        if (!id && allocateNonExist) {
            id = addFile(inode);
        }
        return id;
    }
    uint64_t getFileInodeByID(uint64_t id)
    {
        return inodes.value(id, 0);
    }

private:
    leveldb::DB *db;
    QHash<uint64_t, uint64_t> ids;
    QHash<uint64_t, uint64_t> inodes;
    uint64_t currentIncrementID = 0;
};

FileIDMapper *FileIDMapper::instance()
{
    if (!global_instance) {
        global_instance = new FileIDMapper;
    }
    return global_instance;
}

uint64_t FileIDMapper::addFile(const std::string &uuid, uint64_t inode)
{
    auto database = uuidTable.value(QString::fromStdString(uuid));
    if (!database) {
        return 0;
    }
    uint64_t id = database->addFile(inode);
    return id;
}

bool FileIDMapper::deleteFile(const std::string &uuid, uint64_t inode)
{
    auto database = uuidTable.value(QString::fromStdString(uuid));
    if (!database) {
        return false;
    } else {
        return database->deleteFile(inode);
    }
}

bool FileIDMapper::changeFileInode(const std::string &uuid, uint64_t id, uint64_t inode)
{
    auto database = uuidTable.value(QString::fromStdString(uuid));
    if (!database) {
        return false;
    } else {
        return database->changeFileInode(id, inode);
    }
}

uint64_t FileIDMapper::getFileIDByInode(const std::string &uuid, uint64_t inode, bool allocateNonExisted)
{
    auto database = uuidTable.value(QString::fromStdString(uuid));
    if (!database) {
        return 0;
    } else {
        return database->getFileIDByInode(inode, allocateNonExisted);
    }
}

uint64_t FileIDMapper::getFileInodeByID(const std::string &uuid, uint64_t ID)
{
    auto database = uuidTable.value(QString::fromStdString(uuid));
    if (!database) {
        return 0;
    } else {
        return database->getFileInodeByID(ID);
    }
}

void FileIDMapper::initDataBase(const std::string &uuid)
{
    if (!uuidTable.value(QString::fromStdString(uuid))) {
        uuidTable.insert(QString::fromStdString(uuid), new UUIDFileDatabase(uuid));
    }
}

std::string FileIDMapper::databaseDir()
{
    return dbdir;
}

bool FileIDMapper::filterFileName(const std::string &filename)
{
    for (auto prefix : blackDirList) {
        if (filename.starts_with(prefix)) {
            return true;
        }
    }
    for (auto suffix : blackSuffixList) {
        if (filename.ends_with(suffix)) {
            return true;
        }
    }
    return false;
}

bool FileIDMapper::filterCommand(const std::string &command)
{
    for (auto cmd : blackCmdList) {
        if (command == cmd) {
            return true;
        }
    }
    return false;
}

FileIDMapper::FileIDMapper()
{
    auto dataDir = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/uuid-file-dbs";
    dbdir = dataDir.toStdString();
    QDir dir(dataDir);
    if (!dir.exists()) {
        dir.mkpath(dataDir);
    }
    auto infos = dir.entryInfoList(QStringList() << "*.db");
    for (auto info : infos) {
        QString uuid = info.baseName();
        uuidTable.insert(uuid, new UUIDFileDatabase(uuid.toStdString()));
    }
}
